windows AD 域的登录验证

业务背景

一般 IT 公司都会有很多的软件系统,如果每个系统都有自己的一套用户名和密码,会导致很多不必要的麻烦,每个人要记很多套密码,人员离职时也不方便密码的统一销毁。 最好是一个员工能用一套用户名和密码,登录自己所有的系统。那么用哪个系统的密码比较合适呢?很多公司采用的是 windows AD 域来进行统一密码管理。IT 员工入职以后, 运维的同事都会为他分配一个电脑,并且附带电脑的登录用户名和密码,这个就是 windwos AD 域的用户名和密码。

概念描述

  • AD 域服务:Active Directory 域内的 directory database(目录数据库)被用来存储用户账户、计算机账户、打印机和共享文件夹等对象,我们可以简单地把它理解为 一个树形的目录数据库。

  • LDAP:LDAP(Lightweight Directory Access Protocol),轻量目录访问协议,是一种用来查询与更新 Active Directory 的目录服务通信协议。LDAP 就是我们用来 访问 windsows AD 域数据库的一种技术手段。

LDAP 具体细节大家自行百度,这里主要讲 pigx 实现 LDAP 登录验证实战案例。

实现方式

  1. 在 pom 文件中引入 jar 包
<!-- LDAP集成 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-ldap</artifactId>
</dependency>
  1. 在 yaml 文件配置 LDAP 登录信息
spring:
  ldap:
    urls: ldap://192._._.*:389
    base: ou=test,dc=test,dc=com
    username: ENC(pujnpasdfa/79jkka/iKqer==)
    password: ENC(z1H0sasfafT+rjrtwerqSVjcdNtye5=)
  1. 基于 spring security oauth2 实现自定义 LDAP 密码验证

实现 AuthenticationProvider 接口并且覆写该接口的 authenticate 方法,框架验证密码时会到该方法下面进行密码验证。

@Slf4j
@Component
public class LDAPAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private UserDetailsService adminUserDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private MyLdapProperties myLdapProperties;


    //覆写认证方法
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    	UsernamePasswordAuthenticationToken adminLoginToken = (UsernamePasswordAuthenticationToken) authentication;
    	UserDetails userDetails = adminUserDetailsService.loadUserByUsername(adminLoginToken.getName());
    	log.info(authentication.getPrincipal().toString()+"===================="+authentication.getCredentials().toString());
    	if (ldapLogin(authentication.getPrincipal().toString(), authentication.getCredentials().toString())) {
    		return  new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
    	}
    	throw  new BadCredentialsException("用户名密码不正确");
    }
    @Override
    public boolean supports(Class<?> authentication) {
    	return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }

    private boolean ldapLogin(String username, String password) throws AuthenticationException {
    	long start = System.currentTimeMillis();
    	Hashtable<String, String> HashEnv = new Hashtable<>();
    	HashEnv.put(Context.PROVIDER_URL, myLdapProperties.getUrls());//改成实际ldap配置信息
    	// LDAP访问安全级别(none,simple,strong)
    	HashEnv.put(Context.SECURITY_AUTHENTICATION, "simple");
    	//AD的用户名
    	HashEnv.put(Context.SECURITY_PRINCIPAL, username);//改成实际ldap配置信息
    	//AD的密码
    	HashEnv.put(Context.SECURITY_CREDENTIALS, password);//改成实际ldap配置信息
    	HashEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
    	//连接超时设置为3秒
    	HashEnv.put("com.sun.jndi.ldap.connect.timeout", "3000");
    	//env.put("com.sun.jndi.ldap.connect.timeout", 3000L);
     ctx = null;
    Control[] connCtls = null;
      LdapContext ctx = new InitialLdapContext(HashEnv, connCtls);
      log.info( "认证成功" );
    	log.info("----------------"+(System.currentTimeMillis()-start));
    	return true;
    }

}
  1. 将 LDAP 密码验证 LDAPAuthenticationProvider 添加到 WebSecurityConfigurer 配置中,使之生效
@Primary
@Order(90)
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Autowired
    private AdminPwdAuthenticationProvider adminPwdAuthenticationProvider;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //此处注入一个自定义的LDAP密码验证组件
        auth.authenticationProvider(adminPwdAuthenticationProvider);
    }

    //以下无关代码省略.......

}